package com.sz.biz.logistics.core.service.impl;


import com.sz.biz.common.*;
import com.sz.biz.common.approval.Approval;
import com.sz.biz.common.approval.ApprovalService;
import com.sz.biz.common.approval.ApprovalStatus;
import com.sz.biz.common.base.dto.CommonAreaList;
import com.sz.biz.common.base.dto.CountryArea;
import com.sz.biz.common.base.dto.CurrencyDto;
import com.sz.biz.common.base.entity.FeeType;
import com.sz.biz.common.base.entity.SettlementType;
import com.sz.biz.common.base.service.AreaService;
import com.sz.biz.common.base.service.CurrencyService;
import com.sz.biz.common.base.service.FeeTypeService;
import com.sz.biz.common.base.service.SettlementTypeService;
import com.sz.biz.common.constants.ErrorCodes;
import com.sz.biz.common.constants.ModuleNames;
import com.sz.biz.common.customer.entity.CusCustomer;
import com.sz.biz.common.customer.service.CustomerService;
import com.sz.biz.logistics.base.dto.RegionSchemaDto;
import com.sz.biz.logistics.base.dto.WeightSchemaDto;
import com.sz.biz.logistics.base.entity.RegionSchema;
import com.sz.biz.logistics.base.entity.WeightSchema;
import com.sz.biz.logistics.base.service.RegionSchemaService;
import com.sz.biz.logistics.base.service.WeightSchemaService;
import com.sz.biz.logistics.billing.dto.CalcTypeEnum;
import com.sz.biz.logistics.constants.QuotationConstants;
import com.sz.biz.logistics.core.dto.*;
import com.sz.biz.logistics.core.entity.*;
import com.sz.biz.logistics.core.service.*;
import com.sz.common.base.constants.UserActions;
import com.sz.common.base.dao.ParamData;
import com.sz.common.base.dto.QResultDto;
import com.sz.common.base.dto.QueryParams;
import com.sz.common.base.exception.BizException;
import com.sz.common.base.exception.CommonErrorCodes;
import com.sz.common.base.exception.Exceptions;
import com.sz.common.base.utils.DateUtils;
import com.sz.common.base.utils.EntityUtils;
import com.sz.common.base.utils.SerializeUtil;
import com.sz.common.base.utils.StringUtils;
import com.sz.common.core.service.PrincipalLogUtils;
import com.sz.common.core.service.PrincipalUtils;
import com.sz.common.core.system.entity.SysDict;
import com.sz.common.core.system.service.SysDictService;
import com.sz.common.core.system.service.SysUserSupport;
import com.sz.common.core.system.service.UserModel;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.text.ParseException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Function: QuotationServiceImpl. <br>
 * Date: 2017年03月07日 17:22 <br>
 * Author: wentao.chang
 */
@Service
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class QuotationServiceImpl extends AbstractVersionEntityService implements QuotationService, TimelineCallback {

    private static final BigDecimal MAX_VALUE = new BigDecimal("9999999999");
    private static final String NULL_UID = "-1";
    @Autowired
    private PriceQuotationStartService priceQuotationStartService;
    @Autowired
    private PriceQuotationItemService priceQuotationItemService;
    @Autowired
    private PriceFreightRegionSettingService priceFreightRegionSettingService;
    @Autowired
    private PriceFreightWeightSettingService priceFreightWeightSettingService;

    @Autowired
    private SettlementTypeService settlementTypeService;
    @Autowired
    private CurrencyService currencyService;

    @Autowired
    private FeeTypeService feeTypeService;

    @Autowired
    private SysUserSupport userSupport;

    @Autowired
    private SysDictService dictService;

    @Autowired
    private AreaService areaService;

    @Autowired
    private ApprovalService approvalService;

    @Autowired
    private QuotationSetService quotationSetService;

    @Autowired
    private ServiceService serviceService;

    @Autowired
    private ProductService productService;

    @Autowired
    private CustomerService customerService;

    @Override
    protected String getMapperNamespace() {
        return "QuotationMapper";
    }

    /**
     * base :名称，编码，费用类型，起运地(起点)，分区方案uid，重量段方案uid，货币，备注以及价格的等级客户等信息
     *
     * @param quotationDto quotation
     * @return uid
     */
    @Override
    public QuotationDto saveMain(QuotationDto quotationDto) throws ParseException {
        checkedMain(quotationDto);
        super.save(quotationDto);
        quotationDto.setCode(quotationDto.getId() + "");
        updateCode(quotationDto);
        //保存起点
        saveStarts(quotationDto.getId(), quotationDto.getAreas());
        //保存操作日志
        Map<String, Object> dataBackup = new HashMap<>(16);
        dataBackup.put("ADD_QUOTATION", quotationDto);
        PrincipalLogUtils.addOperationLog(ModuleNames.BIZ, ModuleNames.BIZ_QUOTATION, UserActions.ADD, "添加价格", dataBackup);

        return findActivedByUid(quotationDto.getUid());

    }

    /**
     * base :名称，编码，费用类型，起运地(起点)，分区方案uid，重量段方案uid，货币，备注以及价格的等级客户等信息
     *
     * @param quotationDto quotation
     * @return uid
     */
    @Override
    public QuotationDto updateMain(String uid, QuotationDto quotationDto, boolean isFromAudit) throws ParseException {
        Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
        checkCannotEdit(quotation, isFromAudit);
        checkedMain(quotationDto);
        //更新主表
        quotationDto.setUid(uid);
        quotationDto.setId(quotation.getId());
        quotationDto.setLastUpdateUserId(PrincipalUtils.getAccountId());
        dao.update("QuotationMapper.updateMain", quotationDto);

        //重新保存起点
        saveStarts(quotation.getId(), quotationDto.getAreas());
        if (quotation.getType() == QuotationConstants.Type.COST) {
            //分区设置 重量段设置 货币变化就清空
            if ((!quotationDto.getRegionSchemaUid().equals(quotation.getRegionSchemaUid())) ||
                    !quotationDto.getCurrencyCode().equals(quotation.getCurrencyCode()) ||
                    !quotationDto.getWeightSchemaUid().equals(quotation.getWeightSchemaUid())
                    ) {
                //清空
                quotationSetService.deletedByQuotationId(quotation.getId());
                priceQuotationItemService.deletedByQuotationId(quotation.getId());
                priceFreightRegionSettingService.deletedByQuotationId(quotation.getId());
                priceFreightWeightSettingService.deletedByQuotationId(quotation.getId());
            }

            boolean isOnlyBizChange = !quotationDto.getBizUid().equals(quotation.getBizUid()) &&
                    (quotationDto.getRegionSchemaUid().equals(quotation.getRegionSchemaUid()) &&
                            quotationDto.getCurrencyCode().equals(quotation.getCurrencyCode()) &&
                            quotationDto.getWeightSchemaUid().equals(quotation.getWeightSchemaUid()));
            if (isOnlyBizChange) {
                //更新set的supplierId
                List<QuotationSet> sets = quotationSetService.findByQuotationId(quotation.getId());
                if (!CollectionUtils.isEmpty(sets)) {
                    QuotationSet set = sets.get(0);
                    com.sz.biz.logistics.core.entity.Service service = serviceService.findActivedByUid(quotationDto.getBizUid());
                    set.setSupplierId(service.getSupplierId());
                    quotationSetService.update(set);
                }

            }
        }
        if (quotation.getType() == QuotationConstants.Type.SELLING) {
            //业务id发生变化，就清空
            if (!quotationDto.getBizUid().equals(quotation.getBizUid())) {
                //清空
                quotationSetService.deletedByQuotationId(quotation.getId());
                priceQuotationItemService.deletedByQuotationId(quotation.getId());
                priceFreightRegionSettingService.deletedByQuotationId(quotation.getId());
                priceFreightWeightSettingService.deletedByQuotationId(quotation.getId());
            }
        }

        //保存操作日志
        Map<String, Object> dataBackup = new HashMap<>(16);
        dataBackup.put("UPDATE_QUOTATION", quotationDto);
        PrincipalLogUtils.addOperationLog(ModuleNames.BIZ, ModuleNames.BIZ_QUOTATION, UserActions.UPDATE, "修改价格", dataBackup);

        return findActivedByUid(uid);
    }

    /**
     * base :明细
     * sheet:生效时间 失效时间 表格数据
     *
     * @param uid         uid
     * @param setDto      setDto
     * @param isFromAudit isFromAudit
     * @return uid
     */
    @Override
    public QuotationSet saveDetail(String uid, QuotationSetDto setDto, boolean isFromAudit) throws ParseException {
        Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
        if (quotation == null) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_NOT_FOUND, uid);
        }
        //检查
        checkedSet(quotation, setDto);
        checkCannotEdit(quotation, isFromAudit);

        QuotationSet set;
        set = (setDto.getId() == null ? new QuotationSet() : quotationSetService.findById(setDto.getId()));
        set.setFeeTypeId(setDto.getFeeTypeId());
        set.setWeightSchemaUid(setDto.getWeightSchemaUid());
        set.setRegionSchemaUid(setDto.getRegionSchemaUid());
        set.setQuotationId(quotation.getId());
        if (quotation.getType() == QuotationConstants.Type.COST) {
            set.setSupplierId(setDto.getSupplierId());
        }
        if (quotation.getType() == QuotationConstants.Type.SELLING) {
            set.setServiceUid(setDto.getServiceUid());
        }

        if (set.getId() == null) {
            quotationSetService.save(set);
        } else {
            quotationSetService.update(set);
        }
        //新增明细
        List<FreightItem> items = new ArrayList<>();
        for (FreightItemDto itemDto : setDto.getFreightItems()) {
            FreightItem item = new FreightItem();
            BeanUtils.copyProperties(itemDto, item);
            items.add(item);
        }
        saveItems(quotation.getId(), set.getId(), items);
        //保存分区重量段设置
        saveRegionSettings(quotation.getId(), set.getId(), setDto.getFreightRegionSettings());

        List<PriceFreightWeightSetting> wsItems = new ArrayList<>();
        for (PriceFreightWeightSettingDto itemDto : setDto.getFreightWeightSettings()) {
            PriceFreightWeightSetting item = new PriceFreightWeightSetting();
            BeanUtils.copyProperties(itemDto, item);
            wsItems.add(item);
        }
        saveWeightSettings(quotation.getId(), set.getId(), wsItems);
        return set;
    }


    private void checkCannotEdit(Quotation quotation, boolean isFromAudit) {
        if (isFromAudit) {
            if (quotation.getStatus() == QuotationConstants.Status.AUDIT_PASSED &&
                    quotation.getIsOnline() != null && quotation.getIsOnline()) {
                throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_HAS_ONLINEED, quotation.getCode());
            }
        } else {
            if (quotation.getStatus() == QuotationConstants.Status.SUBMIT_AUDIT) {
                throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_HAS_SUBMITTED, quotation.getCode());
            }
            if (quotation.getStatus() == QuotationConstants.Status.AUDIT_PASSED) {
                throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_HAS_AUDITED, quotation.getCode());
            }
        }
    }

    /**
     * 根据条件查询实体
     *
     * @param params 查询的参数集
     * @return 查询出的数据
     */
    @Override
    public QResultDto findByFilter(QueryParams params) {
        ParamData pd = this.convertQueryParams(params);
        String starts = pd.get("starts").toString();
        areaService.handlerQueryParam(pd, starts);

        List<Quotation> quotations = dao.findForList("QuotationMapper.findByFilterListPage", pd, Quotation.class);
        List<QuotationDto> sDtos = new ArrayList<>();
        CommonAreaList allList = new CommonAreaList();
        for (Quotation quotation : quotations) {
            QuotationDto dto = compositQuotationDto(quotation);
            dto.setStatusName(getStatusName(dto.getStatus(), dto.getIsOnline()));
            List<QuotationStart> areas = priceQuotationStartService.findByQuotationId(quotation.getId());
            CommonAreaList list = new CommonAreaList(areas);
            dto.setAreas(list);
            allList.addAll(list);
            dto.setRegionName(getRegionName(dto.getRegionSchemaUid()));
            compositWeightName(dto);
            sDtos.add(dto);
        }
        //批量填充地区名称
        areaService.fillCommonAreaNames(allList);
        for (QuotationDto dto : sDtos) {
            compositState(dto);
            if (!CollectionUtils.isEmpty(dto.getAreas())) {
                CommonAreaList list = (CommonAreaList) dto.getAreas();
                dto.setStarts(list.joinName("，"));
            }
        }
        return new QResultDto(sDtos, pd.getPagination());
    }


    /**
     * 上线
     *
     * @param uids uids
     */
    @Override
    public void online(List<String> uids, boolean isConfirmed) throws ParseException {
        //处理时间截取
        for (String uid : uids) {
            Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
            if (quotation.getIsOnline() != null && quotation.getIsOnline()) {
                throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_HAS_ONLINEED, quotation.getCode());
            }
            //提交流程
            ApprovalStatus fromStatus;
            if (quotation.getIsOnline() == null) {
                fromStatus = ApprovalStatus.Approved;
            } else {
                fromStatus = ApprovalStatus.Offline;
            }
            approvalService.approval(ApprovalService.ENTITY_TYPE_PRICE, uid,
                    fromStatus, ApprovalStatus.Online, "", PrincipalUtils.getAccountId());
            handlerTimeline(quotation, isConfirmed);
        }
        Map<String, Object> map = new HashMap<>(16);
        map.put("lastUpdateUserId", PrincipalUtils.getAccountId());
        map.put("uids", uids);
        dao.update("QuotationMapper.online", map);
    }

    /**
     * 下线
     *
     * @param uids uids
     */
    @Override
    public void offline(List<String> uids) throws ParseException {
        //将uid+isActive=1的复制一份新的
        Date date = dao.getDbDate();
        for (String uid : uids) {
            Quotation oldQuotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
            if (!oldQuotation.getIsOnline()) {
                throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_HAS_OFFLINEED, oldQuotation.getCode());
            }
            //生成新版本
            Quotation newQuotation = new Quotation();
            BeanUtils.copyProperties(oldQuotation, newQuotation);
            newQuotation.setId(-1);
            newQuotation.setActiveTime(date);
            newQuotation.setIsOnline(false);
            super.save(newQuotation);
            backUpSubTable(oldQuotation.getId(), newQuotation.getId());

            //下线旧版本
            offline(oldQuotation.getId(), date);
            //提交流程
            approvalService.approval(ApprovalService.ENTITY_TYPE_PRICE, uid,
                    ApprovalStatus.Online, ApprovalStatus.Offline, "", PrincipalUtils.getAccountId());
        }
    }

    private void offline(int id, Date date) {
        Map<String, Object> map = new HashMap<>(16);
        map.put("lastUpdateUserId", PrincipalUtils.getAccountId());
        map.put("id", id);
        map.put("inactiveTime", date);
        dao.update("QuotationMapper.offline", map);
    }

    private void backUpSubTable(int oldId, int newId) {
        //子表的备份
        //起点
        List<QuotationStart> starts = priceQuotationStartService.findByQuotationId(oldId);
        backupStarts(newId, starts);

        List<QuotationSet> sets = quotationSetService.findByQuotationId(oldId);
        for (QuotationSet set : sets) {
            int oldSetId = set.getId();
            set.setId(null);
            set.setQuotationId(newId);
            quotationSetService.save(set);
            int newSetId = set.getId();
            //明细项
            List<FreightItem> items = priceQuotationItemService.findByQuotationSetId(oldSetId);
            saveItems(newId, newSetId, items);

            //两个设置
            List<PriceFreightRegionSetting> rSettings = priceFreightRegionSettingService.findByQuotationSetId(oldSetId);
            saveRegionSettings(newId, newSetId, rSettings);
            List<PriceFreightWeightSetting> wSettings = priceFreightWeightSettingService.findByQuotationSetId(oldSetId);
            saveWeightSettings(newId, newSetId, wSettings);
        }

    }

    private void backupStarts(int newId, List<QuotationStart> starts) {
        priceQuotationStartService.deletedByQuotationId(newId);
        if (!CollectionUtils.isEmpty(starts)) {
            for (QuotationStart item : starts) {
                item.setQuotationId(newId);
                item.setId(0);
            }
            priceQuotationStartService.batchsave(starts);
        }
    }

    /**
     * 打回
     *
     * @param uids uids
     */
    @Override
    public void reject(List<String> uids, String msg) throws ParseException {
        List<String> uidsSaved = new ArrayList<>();
        for (String uid : uids) {
            Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
            if (quotation.getStatus() == 2) {
                uidsSaved.add(uid);
                //审核日志
                approvalService.approval(ApprovalService.ENTITY_TYPE_PRICE, uid,
                        ApprovalStatus.Submitted, ApprovalStatus.Draft, msg, PrincipalUtils.getAccountId());
            }
        }
        if (CollectionUtils.isEmpty(uidsSaved)) {
            return;
        }
        Map<String, Object> map = new HashMap<>(16);
        map.put("status", 1);
        map.put("uids", uidsSaved);
        map.put("lastUpdateUserId", PrincipalUtils.getAccountId());
        map.put("isRejected", true);
        dao.update("QuotationMapper.updateStatus", map);

        //保存操作日志
        Map<String, Object> dataBackup = new HashMap<>(16);
        dataBackup.put("UPDATE_QUOTATION", map);
        PrincipalLogUtils.addOperationLog(ModuleNames.BIZ, ModuleNames.BIZ_QUOTATION, UserActions.UPDATE, "打回价格", dataBackup);
    }

    /**
     * 提交
     *
     * @param uids uids
     */
    @Override
    public void submit(List<String> uids) {
        List<String> uidsSaved = new ArrayList<>();
        for (String uid : uids) {
            Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
            if (quotation.getStatus() == 1) {
                uidsSaved.add(uid);
                //提交流程
                approvalService.approval(ApprovalService.ENTITY_TYPE_PRICE, quotation.getUid(),
                        ApprovalStatus.Draft, ApprovalStatus.Submitted, "", PrincipalUtils.getAccountId());
            }
        }
        if (CollectionUtils.isEmpty(uidsSaved)) {
            return;
        }
        Map<String, Object> map = new HashMap<>(16);
        map.put("status", 2);
        map.put("uids", uidsSaved);
        map.put("lastUpdateUserId", PrincipalUtils.getAccountId());
        map.put("isRejected", false);
        dao.update("QuotationMapper.updateStatus", map);
        //保存操作日志
        Map<String, Object> dataBackup = new HashMap<>(16);
        dataBackup.put("UPDATE_QUOTATION", map);
        PrincipalLogUtils.addOperationLog(ModuleNames.BIZ, ModuleNames.BIZ_QUOTATION, UserActions.UPDATE, "提交价格", dataBackup);
    }

    /**
     * 审核
     *
     * @param uids uids
     */
    @Override
    public void audit(List<String> uids, String msg) throws ParseException {
        List<String> uidsSaved = new ArrayList<>();
        for (String uid : uids) {
            Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
            if (quotation.getStatus() == 2) {
                uidsSaved.add(uid);
                //审核日志
                approvalService.approval(ApprovalService.ENTITY_TYPE_PRICE, uid,
                        ApprovalStatus.Submitted, ApprovalStatus.Approved, msg, PrincipalUtils.getAccountId());
            }
        }
        if (CollectionUtils.isEmpty(uidsSaved)) {
            return;
        }
        //先置为审核状态
        Map<String, Object> map = new HashMap<>(16);
        map.put("status", 3);
        map.put("uids", uidsSaved);
        map.put("lastUpdateUserId", PrincipalUtils.getAccountId());
        map.put("isRejected", false);
        dao.update("QuotationMapper.updateStatus", map);
        //保存操作日志
        Map<String, Object> dataBackup = new HashMap<>(16);
        dataBackup.put("UPDATE_QUOTATION", map);
        PrincipalLogUtils.addOperationLog(ModuleNames.BIZ, ModuleNames.BIZ_QUOTATION, UserActions.UPDATE, "审核价格", dataBackup);

        //审核之后直接上线
        //online(uidsSaved);
    }

    /**
     * 删除
     *
     * @param uids uids
     */
    @Override
    public void delete(List<String> uids) {
        List<String> uidsSaved = new ArrayList<>();
        for (String uid : uids) {
            Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
            if (quotation.getStatus() == 1) {
                uidsSaved.add(uid);
            }
        }
        if (CollectionUtils.isEmpty(uidsSaved)) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_NOT_SAVED);
        }
        Date date = dao.getDbDate();
        Map<String, Object> map = new HashMap<>(16);
        map.put("lastUpdateUserId", PrincipalUtils.getAccountId());
        map.put("uids", uidsSaved);
        map.put("inactiveTime", date);
        dao.update("QuotationMapper.deleteByUids", map);
        //保存操作日志
        Map<String, Object> dataBackup = new HashMap<>(16);
        dataBackup.put("DELETE_QUOTATION", map);
        PrincipalLogUtils.addOperationLog(ModuleNames.BIZ, ModuleNames.BIZ_QUOTATION, UserActions.DELETE, "删除价格", dataBackup);
    }


    /**
     * 获取uids生效的报价方案主要信息
     *
     * @param uids uids
     */
    @Override
    public List<Quotation> findActivedByUids(List<String> uids) {
        if (CollectionUtils.isEmpty(uids)) {
            return null;
        }
        Map<String, Object> map = new HashMap<>(16);
        map.put("uids", uids);
        return dao.findForList("QuotationMapper.findActivedByUids", map, Quotation.class);
    }

    private void compositState(QuotationDto dto) {
        if (dto.getGradeType() == QuotationConstants.GradeType.GRADE) {
            dto.setGradeTypeName("等级价");
        }

        if (dto.getGradeType() == QuotationConstants.GradeType.AGREEMENT) {
            dto.setGradeTypeName("协议价");
        }
    }

    /**
     * 根据uid查询可用生效的实体
     *
     * @param uid  uid
     * @param time 时间点
     * @return 查询出来的实体对象
     */
    @Override
    public QuotationDto findByUidAndTime(String uid, Date time) {
        Map<String, String> map = new HashMap<>(16);
        map.put("uid", uid);
        map.put("time", DateUtils.formatDetailDate(time));
        Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findByUidAndTime", map);
        if (quotation == null) {
            return null;
        }
        QuotationDto dto = compositQuotationDto(quotation);
        compositArea(quotation, dto);
        //compositItems(quotation, dto);
        compositState(dto);
        //compositFeeTypeIdxName(dto);
        dto.setRegionName(getRegionName(dto.getRegionSchemaUid()));
        compositWeightName(dto);
        return dto;
    }


    private void compositWeightName(QuotationDto dto) {
        WeightSchema weightSchema =
                (WeightSchema) dao.findForObject("WeightSchemaMapper.findActivedByUid", dto.getWeightSchemaUid());
        if (weightSchema != null) {
            dto.setWeightName(weightSchema.getName());
        }
    }

    private void compositWeightName(QuotationSetDto dto) {
        WeightSchema weightSchema =
                (WeightSchema) dao.findForObject("WeightSchemaMapper.findActivedByUid", dto.getWeightSchemaUid());
        if (weightSchema != null) {
            dto.setWeightName(weightSchema.getName());
        }
    }

    private String getRegionName(String regionSchemaUid) {
        if (NULL_UID.equals(regionSchemaUid)) {
            return null;
        }
        RegionSchema regionSchema = regionSchemaService.findSimpleActivedByUid(regionSchemaUid);
        if (regionSchema != null) {
            return regionSchema.getName();
        }
        return null;
    }

    /**
     * 根据id查询实体
     *
     * @param id id
     * @return 查询出来的实体对象
     */
    @Override
    public Quotation findById(int id) {
        return (Quotation) dao.findForObject("QuotationMapper.findById", id);
    }

    /**
     * 获取uid生效的报价方案主要信息
     *
     * @param uid uid
     */
    @Override
    public QuotationDto findActivedByUid(String uid) {
        Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
        if (quotation == null) {
            return null;
        }
        QuotationDto dto = compositQuotationDto(quotation);
        dto.setStatusName(getStatusName(dto.getStatus(), dto.getIsOnline()));
        compositArea(quotation, dto);
        compositState(dto);
        dto.setRegionName(getRegionName(dto.getRegionSchemaUid()));
        compositWeightName(dto);

        if (dto.getEndEffectTime().compareTo(new Date()) <= 0) {
            dto.setEnable(false);
        } else {
            dto.setEnable(true);
        }
        List<QuotationSet> sets = quotationSetService.findByQuotationId(quotation.getId());
        Map<String, QuotationSet> setMap =
                EntityUtils.getStringKeyMapFromBeans(sets, quotation.getType() == 1 ? "supplierId" : "serviceUid");
        //成本价设置服务的供应商
        if (quotation.getType() == 1) {
            ServiceDto sdto = serviceService.findDtoActivedByUid(quotation.getBizUid());

            QuotationSetDto setDto = new QuotationSetDto();
            setDto.setQuotationId(quotation.getId());
            setDto.setSupplierId(sdto.getSupplierId());
            setDto.setSupplierOrServiceName(sdto.getSupplierName());
            if (!CollectionUtils.isEmpty(sets)) {
                //单供应商
                QuotationSet set = setMap.get(sdto.getSupplierId() + "");
                setDto.setId(set.getId());
            }
            dto.getCostSets().add(setDto);
        }

        //设置产品的服务
        if (quotation.getType() == QuotationConstants.Type.SELLING ) {
            List<ProductServiceRel> servicedtos =
                    productService.findServiceDetailsByUidAndDate(quotation.getBizUid(), new Date(), true);

            //组装二级的服务类型
            for (ProductServiceRel servicedto : servicedtos) {

                QuotationSetDto setDto = new QuotationSetDto();
                setDto.setSupplierOrServiceName(servicedto.getServiceName());
                setDto.setServiceUid(servicedto.getServiceUid());
                setDto.setQuotationId(quotation.getId());
                QuotationSet set = setMap.get(servicedto.getServiceUid());
                if (set != null) {
                    setDto.setId(set.getId());
                }
                String pName = "";
                if (ProductServicePropertyEnum.MAIN.getCode().equals(servicedto.getServiceProperty().getCode())) {
                    pName = ProductServicePropertyEnum.MAIN.getName();
                }
                if (ProductServicePropertyEnum.REQUIRED.getCode().equals(servicedto.getServiceProperty().getCode())) {
                    pName = ProductServicePropertyEnum.REQUIRED.getName();
                }
                if (ProductServicePropertyEnum.OPTIONAL.getCode().equals(servicedto.getServiceProperty().getCode())) {
                    pName = ProductServicePropertyEnum.OPTIONAL.getName();
                }
                String stKey = servicedto.getOrdinal() + "." + servicedto.getServiceTypeName() + "(" + pName + ")";
                List<QuotationSetDto> setDtos = null;
                for (QuotationServiceTypesDto saleSet : dto.getSaleSets()) {
                    if (saleSet.getServiceTypesName().equals(stKey)) {
                        setDtos = saleSet.getSets();
                    }
                }
                if (setDtos == null) {
                    QuotationServiceTypesDto stDto = new QuotationServiceTypesDto();
                    stDto.setServiceTypesName(stKey);
                    dto.getSaleSets().add(stDto);

                    setDtos = stDto.getSets();
                }
                setDtos.add(setDto);
            }
        }

        return dto;
    }

    /**
     * 根据ids查询实体
     *
     * @param ids ids
     * @return 查询出来的实体对象
     */
    @Override
    public List<Quotation> findByIds(List<Integer> ids) {
        return dao.findForList("QuotationMapper.findByIds", ids, Quotation.class);
    }

    /**
     * 根据产品的UID查询价格等级的列表
     *
     * @param productUid productUid
     * @return List
     */
    @Override
    public List<QuotationGradeDto> findQuotationGrades(String productUid) {
        List<Quotation> quotations = dao.findForList("QuotationMapper.findQuotationGrades", productUid, Quotation.class);
        List<QuotationGradeDto> result = new ArrayList<>();
        for (Quotation quotation : quotations) {
            if (quotation.getGrade() == 1) {
                result.add(new QuotationGradeDto(1, "一级"));
            }
            if (quotation.getGrade() == 2) {
                result.add(new QuotationGradeDto(2, "二级"));
            }
            if (quotation.getGrade() == 3) {
                result.add(new QuotationGradeDto(3, "三级"));
            }
            if (quotation.getGrade() == 4) {
                result.add(new QuotationGradeDto(4, "四级"));
            }
            if (quotation.getGrade() == 5) {
                result.add(new QuotationGradeDto(5, "五级"));
            }
        }
        return result;
    }

    /**
     * 按报价方案setid查询报价明细
     *
     * @param quotationUid quotationUid
     * @param setId        setId
     */
    @Override
    public QuotationSetDto findQuotationSetBySetId(String quotationUid, int setId) {
        if (setId == 0) {
            return new QuotationSetDto();
        }
        Quotation quotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", quotationUid);
        if (quotation == null) {
            return new QuotationSetDto();
        }
        QuotationSet set = quotationSetService.findById(setId);
        QuotationSetDto setDto = new QuotationSetDto();
        BeanUtils.copyProperties(set, setDto);
        compositFeeTypeIdxName(setDto);
        compositWeightName(setDto);
        setDto.setRegionName(getRegionName(setDto.getRegionSchemaUid()));
        compositItems(setDto);
        return setDto;
    }

    private void compositFeeTypeIdxName(QuotationSetDto setdto) {
        FeeType ft = feeTypeService.findById(setdto.getFeeTypeId());
        if (ft != null) {
            setdto.setFeeTypeName(ft.getName());
            setdto.setFeeTypeIdx(ft.getType());
        }
        if (setdto.getFeeTypeId() == null || setdto.getFeeTypeId() == 0) {
            return;
        }
        List<SysDict> feeTypes = dictService.findByCatalog("biz.feetype.type");
        Map<String, SysDict> feeTypeMapper = EntityUtils.getStringKeyMapFromBeans(feeTypes, "code");
        if (feeTypeMapper.get(setdto.getFeeTypeIdx() + "") != null) {
            setdto.setFeeTypeIdxName(feeTypeMapper.get(setdto.getFeeTypeIdx() + "").getName());
        }
    }

    private void compositItems(QuotationSetDto setDto) {
        List<FreightItem> items = priceQuotationItemService.findByQuotationSetId(setDto.getId());
        List<FreightItemDto> itemDtos = new ArrayList<>();
        List<SysDict> calcType = dictService.findByCatalog("biz.price.calctype");
        Map<String, SysDict> calcTypeMapper = EntityUtils.getStringKeyMapFromBeans(calcType, "code");
        List<SysDict> weights = dictService.findByCatalog("sys.unit.weight");
        Map<String, SysDict> weightMapper = EntityUtils.getStringKeyMapFromBeans(weights, "code");
        for (FreightItem item : items) {
            FreightItemDto itemDto = new FreightItemDto();
            BeanUtils.copyProperties(item, itemDto);
            if (!"-1".equals(item.getCalcType())) {
                itemDto.setCalcName(calcTypeMapper.get(item.getCalcType()).getName());
            }
            if (weightMapper.get(item.getBaseUnitCode()) != null) {
                itemDto.setBaseUnitName(weightMapper.get(item.getBaseUnitCode()).getName());
            }
            itemDtos.add(itemDto);
        }
        setDto.setFreightItems(itemDtos);

        List<PriceFreightWeightSetting> wSettings = priceFreightWeightSettingService.findByQuotationSetId(setDto.getId());
        List<PriceFreightWeightSettingDto> wsDtos = new ArrayList<>();
        for (PriceFreightWeightSetting item : wSettings) {
            PriceFreightWeightSettingDto itemDto = new PriceFreightWeightSettingDto();
            BeanUtils.copyProperties(item, itemDto);
            if (!"-1".equals(item.getCalcType())) {
                itemDto.setCalcName(calcTypeMapper.get(item.getCalcType()).getName());
            }
            if (weightMapper.get(item.getBaseUnitCode()) != null) {
                itemDto.setBaseUnitName(weightMapper.get(item.getBaseUnitCode()).getName());
            }
            wsDtos.add(itemDto);
        }
        setDto.setFreightWeightSettings(wsDtos);
    }

    /**
     * 定位报价方案时间轴上所有的报价方案
     */
    @Override
    public List<Quotation> findByBizUid(String bizUid) {
        Map<String, Object> map = new HashMap<>(16);
        map.put("bizUid", bizUid);
        return dao.findForList("QuotationMapper.findByBizUid", map, Quotation.class);
    }

    /**
     * 定位报价方案时间轴上所有的报价方案
     */
    @Override
    public List<Quotation> findByBizUidAndTimeline(Quotation quotation) {
        return dao.findForList("QuotationMapper.findByBizUidAndTimeline", quotation, Quotation.class);
    }

    @Override
    public List<Quotation> findByBizUidAndTime(String bizUid, Date time) {
        Map<String, Object> map = new HashMap<>(16);
        map.put("bizUid", bizUid);
        map.put("time", DateUtils.formatDetailDate(time));
        return dao.findForList("QuotationMapper.findByBizUidAndTime", map, Quotation.class);
    }

    private QuotationDto compositQuotationDto(Quotation quotation) {
        QuotationDto dto = new QuotationDto();
        BeanUtils.copyProperties(quotation, dto);
        SettlementType st = settlementTypeService.findById(dto.getSettlementTypeId());
        if (st != null) {
            dto.setSettlementTypeName(st.getName());
            dto.setSettlementTypeCode(st.getCode());
        }
        if (quotation.getType() == 1) {
            com.sz.biz.logistics.core.entity.Service service =
                    (com.sz.biz.logistics.core.entity.Service)
                            dao.findForObject("ServiceMapper.findActivedByUid", quotation.getBizUid());
            if (service != null) {
                dto.setServiceName(service.getName());
            }
        } else {
            Product product = (Product) dao.findForObject("ProductMapper.findActivedByUid", quotation.getBizUid());
            if (product != null) {
                dto.setProductid(product.getId());
                dto.setProductUid(product.getUid());
                dto.setProductName(product.getName());
            }
            //协议价
            if (quotation.getGradeType() ==QuotationConstants.GradeType.AGREEMENT  && quotation.getCustomerId() != null) {
                CusCustomer customer = customerService.findCustomerById(quotation.getCustomerId());
                dto.setCustomerName(customer.getUserName());
            }
        }

        CurrencyDto currency = currencyService.findSingleCurencyByCode(dto.getCurrencyCode());
        if (currency != null) {
            dto.setCurrencyName(currency.getName());
        }
        if (userSupport != null && quotation.getCreateUserId() != null) {
            UserModel user = userSupport.getUserById(quotation.getCreateUserId());
            if (user != null) {
                dto.setCreatorUserName(user.getFullName() + " " + user.getAccountName());
            }
        }
        List<SysDict> dictList = dictService.findByCatalog("biz.cargo.type");
        Map<String, SysDict> dictMapp = EntityUtils.getStringKeyMapFromBeans(dictList, "code");
        SysDict dict = dictMapp.get(quotation.getCargoTypeCode());
        if (dict != null) {
            dto.setCargoTypeName(dict.getName());
        }

        return dto;
    }

    private void compositArea(Quotation quotation, QuotationDto dto) {
        List<QuotationStart> starts = priceQuotationStartService.findByQuotationId(quotation.getId());
        if (CollectionUtils.isEmpty(starts)) {
            return;
        }

        CommonAreaList list = new CommonAreaList(starts);
        areaService.fillAllPathCommonAreaNames(list);
        dto.setAreas(list);
    }

    private void saveStarts(int quotationId, List<CountryArea> areas) {
        priceQuotationStartService.deletedByQuotationId(quotationId);
        if (!CollectionUtils.isEmpty(areas)) {
            priceQuotationStartService.batchsave(assembleItems(areas, quotationId));
        }
    }


    /**
     * 组装要保存的分区item集合
     *
     * @param areas 里面要保存的分区item数据
     * @param id    　分区的外键id
     * @return List 组装好的分区itemlist集合
     */
    private List<QuotationStart> assembleItems(List<CountryArea> areas, Integer id) {
        List<QuotationStart> itemList = new ArrayList<>();
        for (CountryArea countryArea : areas) { //保存地区的分区方案详情
            String countryCode = countryArea.getCountryCode();
            QuotationStart entry = new QuotationStart();
            areaService.setCountryArea(countryCode, entry);
            entry.setQuotationId(id);
            itemList.add(entry);
        }
        return itemList;
    }

    private void saveItems(int quotationId, int setId, List<FreightItem> items) {
        priceQuotationItemService.deletedByQuotationSetId(setId);
        if (!CollectionUtils.isEmpty(items)) {
            for (FreightItem item : items) {
                item.setQuotationId(quotationId);
                item.setQuotationSetId(setId);
                item.setId(null);
                if (item.getPrice() != null && item.getPrice().compareTo(MAX_VALUE) > 0) {
                    throw Exceptions.bizException(ErrorCodes.ERROR_ARGUMENT_ILLEGAL, "price(最大999999999)");
                }
            }
            priceQuotationItemService.batchsave(items);
        }
    }

    private void saveWeightSettings(int quotationId, int setId, List<PriceFreightWeightSetting> items) {
        priceFreightWeightSettingService.deletedByQuotationSetId(setId);
        if (!CollectionUtils.isEmpty(items)) {
            for (PriceFreightWeightSetting item : items) {
                item.setQuotationId(quotationId);
                item.setQuotationSetId(setId);
                item.setId(null);
            }
            priceFreightWeightSettingService.batchsave(items);
        }
    }

    private void saveRegionSettings(int quotationId, int setId, List<PriceFreightRegionSetting> items) {
        priceFreightRegionSettingService.deletedByQuotationSetId(setId);
        if (!CollectionUtils.isEmpty(items)) {
            for (PriceFreightRegionSetting item : items) {
                item.setQuotationId(quotationId);
                item.setQuotationSetId(setId);
                item.setId(null);
            }
            priceFreightRegionSettingService.batchsave(items);
        }
    }

    /**
     * getType
     * getCargoTypeCode
     * getSettlementTypeId
     * getRegionSchemaUid
     * getWeightSchemaUid
     * getFeeTypeId
     * getGrade
     *
     * @param quotationDto quotationDto
     */
    private void checkedMain(QuotationDto quotationDto) {
        boolean typeError = quotationDto.getType() == null || (quotationDto.getType() != 1 && quotationDto.getType() != 2);
        if (typeError) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "type");
        }
        //成本的 级别默认设置1 结算方式根据供应商确定
        if (quotationDto.getType() == 1) {
            quotationDto.setGradeType(1);
            quotationDto.setGrade(1);
        } else {
            if (quotationDto.getGradeType() == null ||
                    quotationDto.getGradeType() < QuotationConstants.GradeType.GRADE ||
                    quotationDto.getGradeType() > QuotationConstants.GradeType.SET ) {
                throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "gradeType");
            }
            //销售价的级别
            if (quotationDto.getGradeType() == QuotationConstants.GradeType.GRADE) {
                if (quotationDto.getGrade() == null || quotationDto.getGrade() < 1 || quotationDto.getGrade() >  5) {
                    throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "grade");
                }
                quotationDto.setCustomerId(0);
            } else {
                quotationDto.setGrade(1);
                if (quotationDto.getCustomerId() == null || quotationDto.getCustomerId() == 0) {
                    throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "customerId");
                }
            }
        }

        quotationDto.setCargoTypeCode(getCheckedTrimString(quotationDto.getCargoTypeCode(), "cargoTypeCode"));
        quotationDto.setRegionSchemaUid(getCheckedTrimString(quotationDto.getRegionSchemaUid(), "regionSchemaUid"));
        quotationDto.setWeightSchemaUid(getCheckedTrimString(quotationDto.getWeightSchemaUid(), "weightSchemaUid"));
        quotationDto.setBizUid(getCheckedTrimString(quotationDto.getBizUid(), "bizUid"));
        quotationDto.setCurrencyCode(getCheckedTrimString(quotationDto.getCurrencyCode(), "currencyCode"));

        if (quotationDto.getStartEffectTime() == null) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "startEffectTime");
        }
        if (quotationDto.getEndEffectTime() == null) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "endEffectTime");
        }
        if (quotationDto.getStartEffectTime().compareTo(quotationDto.getEndEffectTime()) >= 0) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL,
                    "startEffectTime and endEffectTime");
        }
        quotationDto.setDescription(quotationDto.getDescription() == null ? null : quotationDto.getDescription().trim());
    }


    @Autowired
    private WeightSchemaService weightSchemaService;
    @Autowired
    private RegionSchemaService regionSchemaService;


    private void checkedSet(Quotation quotation, QuotationSetDto setDto) {
        boolean isCostParamError = setDto.getSupplierId() == null || setDto.getSupplierId() == 0;
        if (quotation.getType() == 1 && isCostParamError) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "supplierId");
        }

        if (quotation.getType() == QuotationConstants.Type.SELLING  && StringUtils.isEmpty(setDto.getServiceUid())) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "serviceUid");
        }
        setDto.setWeightSchemaUid(getCheckedTrimString(setDto.getWeightSchemaUid(), "weightSchemaUid"));
        //处理detail
        int regionSize = 1;
        RegionSchemaDto regionSchemaDto = null;
        if (!NULL_UID.equals(setDto.getRegionSchemaUid())) {
            regionSchemaDto = regionSchemaService.findActivedByUid(setDto.getRegionSchemaUid());
            regionSize = regionSchemaDto.getRegionlist().size();
        }
        int weightSize = 1;
        if (!NULL_UID.equals(setDto.getWeightSchemaUid())) {
            WeightSchemaDto weightSchemaDto = weightSchemaService.findActivedByUid(setDto.getWeightSchemaUid());
            weightSize = weightSchemaDto.getWeightSectionList().size();
        }

        if (setDto.getFreightWeightSettings().size() != weightSize) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "freightWeightSettings's size");
        }

        int itemSize = weightSize * regionSize;
        if (itemSize != setDto.getFreightItems().size()) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_ARGUMENT_ILLEGAL, "freightItems size");
        }

        //设置detail的idx
        for (int i = 0; i < itemSize; i++) {
            FreightItem item = setDto.getFreightItems().get(i);
            String toRegionUid = NULL_UID;
            if (regionSchemaDto != null) {
                toRegionUid = regionSchemaDto.getRegionlist().get(i % regionSize).getUid();
            }
            item.setToRegionUid(toRegionUid);

            item.setIdx(i % regionSize + 1);
            item.setOrdinal(i + 1);
            //检查续单价
            if (item.getCalcType().equals(CalcTypeEnum.CONTINUEPRICE.getCode())) {
                //往上搜索
                int lastIndex = i - regionSize;
                if (lastIndex < 0) {
                    throw Exceptions.bizException(
                            com.sz.biz.logistics.constants.ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR,
                            "第一个单价不能为续单价");
                }
                FreightItem firstFreightItem = setDto.getFreightItems().get(lastIndex);
                if (!CalcTypeEnum.TOTALPRICE.getCode().equals(firstFreightItem.getCalcType())) {
                    String msg = "续单价上一个价格设置必须为总价";
                    throw Exceptions.bizException(
                            com.sz.biz.logistics.constants.ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR,
                            msg);
                }

            }
            //检查续阶单价
            if (item.getCalcType().equals(CalcTypeEnum.CONTINUESECTIONPRICE.getCode())) {
                //往上搜索
                int lastIndex = i - regionSize;
                if (lastIndex < 0) {
                    throw Exceptions.bizException(
                            com.sz.biz.logistics.constants.ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR,
                            "第一个单价不能为续阶单价");
                }

                boolean hasPCTSUM = false;
                while (lastIndex >= 0) {
                    FreightItem firstFreightItem = setDto.getFreightItems().get(lastIndex);
                    if ((lastIndex == i - regionSize) &&
                            firstFreightItem.getCalcType().equals(CalcTypeEnum.TOTALPRICE.getCode())) {
                        String msg = "续阶单价与总价之间必须存在单价";
                        throw Exceptions.bizException(
                                com.sz.biz.logistics.constants.ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR,
                                msg);
                    }
                    if (firstFreightItem.getCalcType().equals(CalcTypeEnum.TOTALPRICE.getCode())) {
                        hasPCTSUM = true;
                        break;
                    }
                    if (!firstFreightItem.getCalcType().equals(CalcTypeEnum.PRICE.getCode())) {
                        String msg = "续阶单价与总价之间必须为单价";
                        throw Exceptions.bizException(
                                com.sz.biz.logistics.constants.ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR,
                                msg);
                    }
                    lastIndex -= regionSize;
                }
                if (!hasPCTSUM) {
                    throw Exceptions.bizException(
                            com.sz.biz.logistics.constants.ErrorCodes.ERROR_QUOTATION_CALC_PRICESETTINGERROR,
                            "续阶单价往上没有找到总价");
                }
            }
        }
        for (int i = 0; i < setDto.getFreightWeightSettings().size(); i++) {
            setDto.getFreightWeightSettings().get(i).setOrdinal(i + 1);
        }

    }

    private void handlerTimeline(Quotation quotationTarget, boolean isConfirmed) throws ParseException {
        //按定位报价方案的条件查询是否有多条，有的话就需要用算法判断处理哪些需要新增修改和删除时间轴
        List<Quotation> quotations = findByBizUidAndTimeline(quotationTarget);
        if (CollectionUtils.isEmpty(quotations)) {
            return;
        }
        StringBuilder startsTargetStr = getStartsString(quotationTarget.getId());
        List<Quotation> quotationsFilter = new ArrayList<>();
        for (Quotation quotation : quotations) {
            //比较起点
            StringBuilder startsOnlinedStr = getStartsString(quotation.getId());
            if (startsOnlinedStr.toString().equals(startsTargetStr.toString())) {
                quotationsFilter.add(quotation);
                continue;
            }
            //检查起点是否有交集 有交集会报错禁止启用
            checkStartIntersection(quotationTarget.getId(), quotation.getId(), quotation.getCode());
        }
        if (!CollectionUtils.isEmpty(quotationsFilter)) {
            List<TimelineEntity> timelineEntitys = new ArrayList<>();
            timelineEntitys.addAll(quotationsFilter);
            TimelineAlgorithm alg = new TimelineAlgorithm(true);
            TimelineRelation relation = alg.calcRelation(timelineEntitys, quotationTarget);
            //影响的列表
            Map<String, Object> affectMap = new HashMap<>(16);
            List<Quotation> affected = new ArrayList<>();
            List<Quotation> willDeleted = new ArrayList<>();
            if (relation.getIntersectedLeft() != null) {
                Quotation quotation = (Quotation) relation.getIntersectedLeft();
                if (quotation.getIsOnline() && quotation.getId().intValue() != quotationTarget.getId()) {
                    affected.add(quotation);
                }
            }
            if (relation.getIntersectedRight() != null) {
                Quotation quotation = (Quotation) relation.getIntersectedRight();
                if (quotation.getIsOnline() && quotation.getId().intValue() != quotationTarget.getId()) {
                    affected.add(quotation);
                }
            }
            if (!CollectionUtils.isEmpty(relation.getCoveredEntities())) {
                for (TimelineEntity entity : relation.getCoveredEntities()) {
                    Quotation quotation = (Quotation) entity;
                    if (quotation.getIsOnline() && quotation.getId().intValue() != quotationTarget.getId()) {
                        willDeleted.add(quotation);
                    }
                }
            }
            //未确认先返回确认信息
            boolean isNeedConfirm = !isConfirmed && (affected.size() > 0 || willDeleted.size() > 0);
            if (isNeedConfirm) {
                affectMap.put("affect", affected);
                affectMap.put("willdeleted", willDeleted);
                //返回确认信息
                BizException exception = Exceptions.bizException(
                        ErrorCodes.ERROR_BIZ_QUOTATION_COVERCONFIRM, quotationTarget.getCode());
                exception.setData(affectMap);
                throw exception;
            }
            //更新数据
            alg.updateTimeline(timelineEntitys, quotationTarget, this);
        }
    }

    private StringBuilder getStartsString(int quotationId) {
        List<QuotationStart> targetStarts = priceQuotationStartService.findByQuotationId(quotationId);
        StringBuilder sbTarget = new StringBuilder();
        if (!CollectionUtils.isEmpty(targetStarts)) {
            List<String> starts = targetStarts.stream().map(start -> getStartStr(start).toString()).collect(Collectors.toList());
            starts.sort(String::compareTo);
            starts.forEach(sbTarget::append);
        }
        return sbTarget;
    }

    private void checkStartIntersection(int targetQuotationId, int enabledQuotationId, String enabledQuotationCode) {
        List<QuotationStart> targetStarts = priceQuotationStartService.findByQuotationId(targetQuotationId);
        List<QuotationStart> enabledStarts = priceQuotationStartService.findByQuotationId(enabledQuotationId);
        if (!CollectionUtils.isEmpty(targetStarts) && !CollectionUtils.isEmpty(enabledStarts)) {

            CommonAreaList targetStartCAs = new CommonAreaList(targetStarts);
            areaService.fillAllPathCommonAreaNames(targetStartCAs);
            CommonAreaList enabledStartCAs = new CommonAreaList(enabledStarts);
            areaService.fillAllPathCommonAreaNames(enabledStartCAs);
            try {
                areaService.judgeArea(targetStartCAs, enabledStartCAs);
            } catch (BizException e) {
                String msg = "该价格方案的起运地与已启用的价格方案编码" + enabledQuotationCode + "有部分重叠，不可启用！";
                throw Exceptions.bizExceptionData(ErrorCodes.ERROR_COMMON, enabledQuotationCode, msg);
            }

        }
    }

    private StringBuilder getStartStr(QuotationStart start) {
        StringBuilder areaSb = new StringBuilder();
        //不可能为空
        areaSb.append(start.getType());
        //不可能为空
        areaSb.append(start.getCountry());
        //可能为空
        areaSb.append(start.getAreaId() == null ? "" : start.getAreaId());
        //可能为空
        areaSb.append(start.getStartPostcode() == null ? "" : start.getStartPostcode());
        //可能为空
        areaSb.append(start.getEndPostcode() == null ? "" : start.getEndPostcode());
        //可能为空
        areaSb.append(start.getPolygon() == null ? "" : start.getPolygon());
        areaSb.append("|");
        return areaSb;
    }

    /**
     * 插入进来的是截取出来的
     */
    @Override
    public void insertTimelineEntity(List<TimelineEntity> entities) throws ParseException {

        if (CollectionUtils.isEmpty(entities)) {
            return;
        }
        Date date = dao.getDbDate();
        for (TimelineEntity item : entities) {
            Quotation old = (Quotation) item;
            Integer oldId = old.getId();
            QuotationDto newQuotation = new QuotationDto();
            BeanUtils.copyProperties(old, newQuotation);
            newQuotation.setId(-1);
            newQuotation.setActiveTime(date);
            newQuotation.setUid(null);
            super.save(newQuotation);
            newQuotation.setCode(newQuotation.getId() + "");
            updateCode(newQuotation);
            backUpSubTable(oldId, newQuotation.getId());
            //拷貝歷史流水
            List<Approval> approvals = approvalService.getApprovals(ApprovalService.ENTITY_TYPE_PRICE, old.getUid());

            for (Approval approval : approvals) {
                approval.setId(null);
                approval.setOwnerId(newQuotation.getUid());
                approvalService.addApproval(approval);
            }
            //增加提交流程流水
            //approvalService.approval(ApprovalService.ENTITY_TYPE_PRICE, newQuotation.getUid(),
            //       ApprovalStatus.Offline, ApprovalStatus.Online, "", PrincipalUtils.getAccountId());
        }
    }

    /**
     * 更新的是已有的历史数据 只要更新两个时间字段以及更新人相关的信息
     *
     * @param entities entities
     */
    @Override
    public void updateTimelineEntity(List<TimelineEntity> entities) throws ParseException {
        if (CollectionUtils.isEmpty(entities)) {
            return;
        }
        Date date = dao.getDbDate();
        for (TimelineEntity item : entities) {
            Quotation entity = (Quotation) item;

            //生成新版本
            Quotation newQuotation = new Quotation();
            BeanUtils.copyProperties(entity, newQuotation);
            newQuotation.setId(-1);
            newQuotation.setActiveTime(date);
            newQuotation.setUid(null);
            super.save(newQuotation);

            backUpSubTable(entity.getId(), newQuotation.getId());
            //拷貝歷史流水
            List<Approval> approvals = approvalService.getApprovals(ApprovalService.ENTITY_TYPE_PRICE, entity.getUid());

            for (Approval approval : approvals) {
                approval.setId(null);
                approval.setOwnerId(newQuotation.getUid());
                approvalService.addApproval(approval);
            }
            //下线旧版本
            offline(entity.getId(), date);
        }
    }

    private void updateCode(QuotationDto quotationDto) {
        dao.update("QuotationMapper.updateCode", quotationDto);
    }


    @Override
    public void deleteTimelineEntity(List<TimelineEntity> entities) {
        if (CollectionUtils.isEmpty(entities)) {
            return;
        }
        Date date = dao.getDbDate();
        for (TimelineEntity item : entities) {
            Quotation entity = (Quotation) item;
            //删除旧版本
            Map<String, Object> map = new HashMap<>(16);
            map.put("lastUpdateUserId", PrincipalUtils.getAccountId());
            map.put("uid", entity.getUid());
            map.put("inactiveTime", date);
            dao.update("QuotationMapper.timelineOffline", map);
            //增加提交流程流水
            approvalService.approval(ApprovalService.ENTITY_TYPE_PRICE, entity.getUid(),
                    ApprovalStatus.Online, ApprovalStatus.Offline, "", PrincipalUtils.getAccountId());
        }
    }

    /**
     * 查询所有已经上线但是起点为空的报价
     */
    @Override
    public List<Quotation> findNoneStartOnlineQuotations(int type, Date date) {
        ParamData pd = new ParamData();
        if (date != null) {
            pd.put("time", DateUtils.formatDetailDate(date));
        }
        pd.put("type", type);
        return dao.findForList("QuotationMapper.findNoneStartOnlineQuotations", pd, Quotation.class);
    }

    @Override
    public TimelineEntity cloneTimelineEntity(TimelineEntity entity) {
        if (entity == null) {
            return null;
        }
        byte[] bytes = SerializeUtil.serialize(entity);
        Quotation q = (Quotation) SerializeUtil.unserialize(bytes);
        q.setActiveTime(null);
        return q;
    }

    @Override
    public boolean isRegionSchemaReferenced(String uid, boolean isOnline) {
        if (StringUtils.hasLength(uid)) {
            ParamData pd = new ParamData();
            pd.put("uid", uid);
            pd.put("isOnline", isOnline);
            int count = (int) dao.findForObject("QuotationMapper.findRegionSchemaUidRefCount", pd);
            return count > 0;
        }
        return false;
    }

    /**
     *
     * @param uid       重量段或分区的方案uid
     * @param isOnline  是否在线
     * @return
     */
    @Override
    public boolean isWeightSchemaReferenced(String uid, boolean isOnline) {
        if (StringUtils.hasLength(uid)) {
            ParamData pd = new ParamData();
            pd.put("uid", uid);
            pd.put("isOnline", isOnline);
            int count = (int) dao.findForObject("QuotationMapper.findWeightSchemaUidRefCount", pd);
            return count > 0;
        }
        return false;
    }

    /**
     * 根据销售报价的uid以及服务uid查询此服务的成本价的分区
     *
     * @param quotationUid 报价uid
     * @param serviceUid   服务uid
     * @return QuotationRegionDto 返回的成本的分区信息
     */
    @Override
    public QuotationRegionDto findQuotationRegion(String quotationUid, String serviceUid) {
        //销售报价
        Quotation saleQuotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", quotationUid);

        if (saleQuotation == null) {
            return null;
        }
        QueryParams params = new QueryParams(10, 1, "", null);
        params.put("type", 1);
        params.put("biz", serviceUid);

        params.put("startEffectTime", DateUtils.formatDetailDate(saleQuotation.getStartEffectTime()));
        params.put("endEffectTime", DateUtils.formatDetailDate(saleQuotation.getEndEffectTime()));
        params.put("isOnline", 1);
        params.put("status", 3);
        params.put("currencyCode", 0);
        params.put("feeTypeId", 0);
        params.put("settlementId", 0);
        params.put("orderby", "code");
        ParamData param = convertQueryParams(params);
        List<Quotation> costQuotations = dao.findForList("QuotationMapper.findByFilterListPage", param, Quotation.class);
        if (CollectionUtils.isEmpty(costQuotations)) {
            return null;
        }
        Quotation q = costQuotations.get(0);
        QuotationRegionDto dto = new QuotationRegionDto();
        dto.setRegionName(this.getRegionName(q.getRegionSchemaUid()));
        dto.setRegionUid(q.getRegionSchemaUid());

        List<QuotationSet> sets = quotationSetService.findByQuotationId(q.getId());
        QuotationSet set = sets.get(0);
        dto.setWeightUid(set.getWeightSchemaUid());
        WeightSchema weightSchema =
                (WeightSchema) dao.findForObject("WeightSchemaMapper.findActivedByUid", set.getWeightSchemaUid());
        if (weightSchema != null) {
            dto.setWeightName(weightSchema.getName());
        }


        if (set.getFeeTypeId() != null && set.getFeeTypeId() != 0) {
            dto.setFeeTypeId(set.getFeeTypeId());
            FeeType ft = feeTypeService.findById(set.getFeeTypeId());
            dto.setFeeTypeName(ft.getName());
            dto.setFeeTypeIdx(ft.getType());
            List<SysDict> feeTypes = dictService.findByCatalog("biz.feetype.type");
            Map<String, SysDict> feeTypeMapper = EntityUtils.getStringKeyMapFromBeans(feeTypes, "code");
            if (feeTypeMapper.get(dto.getFeeTypeIdx() + "") != null) {
                dto.setFeeTypeIdxName(feeTypeMapper.get(dto.getFeeTypeIdx() + "").getName());
            }
        }

        return dto;
    }

    /**
     * 复制明细
     *
     * @param uid uid
     * @param dto dto
     */
    @Override
    public QuotationDto copyDetail(String uid, QuotationDetailCopyDto dto) {
        //销售报价
        Quotation saleQuotation = (Quotation) dao.findForObject("QuotationMapper.findActivedByUid", uid);
        if (saleQuotation == null) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_NOT_FOUND, uid);
        }
        List<QuotationSet> sets = quotationSetService.findByQuotationId(saleQuotation.getId());
        Map<String, QuotationSet> map = EntityUtils.getStringKeyMapFromBeans(sets, "serviceUid");
        QuotationSet fromSet = map.get(dto.getFromUid());
        if (fromSet == null) {
            throw Exceptions.bizException(CommonErrorCodes.ERROR_MODEL_NOT_FOUND, dto.getFromUid());
        }
        for (String toUid : dto.getToUids()) {
            QuotationSet toSet = map.get(toUid);
            if (toSet == null) {
                toSet = new QuotationSet();
                EntityUtils.copyPropertiesIgnoreNull(fromSet, toSet);
                toSet.setServiceUid(toUid);
                toSet.setId(null);
                quotationSetService.save(toSet);
            } else {
                toSet.setRegionSchemaUid(fromSet.getRegionSchemaUid());
                toSet.setFeeTypeId(fromSet.getFeeTypeId());
                toSet.setSupplierId(fromSet.getSupplierId());
                toSet.setWeightSchemaUid(fromSet.getWeightSchemaUid());
                quotationSetService.update(toSet);
                priceQuotationItemService.deletedByQuotationSetId(toSet.getId());
                priceFreightWeightSettingService.deletedByQuotationSetId(toSet.getId());
            }
            //重量设置明细
            List<PriceFreightWeightSetting> fromWeightSetting = priceFreightWeightSettingService.findByQuotationSetId(fromSet.getId());
            if (!CollectionUtils.isEmpty(fromWeightSetting)) {
                for (PriceFreightWeightSetting s : fromWeightSetting) {
                    s.setId(null);
                    s.setQuotationSetId(toSet.getId());
                }
                priceFreightWeightSettingService.batchsave(fromWeightSetting);
            }
            //价格明细
            List<FreightItem> fromItems = priceQuotationItemService.findByQuotationSetId(fromSet.getId());
            if (!CollectionUtils.isEmpty(fromItems)) {
                for (FreightItem s : fromItems) {
                    s.setId(null);
                    s.setQuotationSetId(toSet.getId());
                }
                priceQuotationItemService.batchsave(fromItems);
            }
        }
        return findActivedByUid(uid);
    }
}
